; uber optimized version
; opcodes are actually offsets into opcode page.  from there, can have inline assembly or jump to another location.
;  what's the big advantage?  vmnext is just 15 cycles. (down from 31 in y_s)  and this arrangement saves a LOT of space.
;  what's the big disadvantage?  # of core opcodes is quite restricted now.
;  secondary advantage: vmnext is so simple we've freed up the 6502 accumulator to be TOS again.
; i'm pretty sure that this is as efficient as it can possibly get before it would become useless.


; bytecode is tight, but slow.
; one way to utilize it better is to stagger processing
;  for instance cycle through your entities, or update certain things on even frames and others on odd frames
; you'll probably end up rewriting many routines in asm based on ease, frequency of use, and need.
; so bytecode serves three main purposes: rapid prototyping, size savings, and non-speed-critical code (like decompression)

;Virtual Machine Register descriptions and implementation
;data stack     6502 stack
;TOS            A - "top of stack"
;RSP            zp pointer (8) - return stack pointer
;IPBASE         zp pointer (16)
;IP             (IPBASE), Y - bytecode routines cannot be more than 256 bytes long
;A              zp pointer (16) - auto incrementing src/dest address or temp storage for routines.
;B              zp pointer (16) - volatile general purpose src/dest address.  no direct access opcodes.
;O              "object" register used in conjuction with literals to work with interleaved objects

;6502 X is free at the asm level but destroyed by VMNEXT

VM_BTM =$00
VM_TOP =$30

ENUM VM_BTM
	VM_RSP:  .db 0	   ; 8-bit return stack pointer
	VM_TEMP: .dw 0    ; work variable
	VM_A:    .dw 0
	VM_B:    .dw 0
	VM_SPTMP:.db 0
	VM_O:		.dw 0    ; high byte is kept 0
	VM_EXTVEC:.dw 0
	VM_RTOP = VM_TOP - VMNEXT_SIZE ; grows downward, pre-decremented.  ~8 levels
	VM_NEXT = (VM_TOP - VMNEXT_SIZE) + 1
   VM_IPBASE = VM_NEXT + 1
	VMNEXT_JMP = VM_NEXT + 7
ENDE

macro init_vm
	; copy inner loop to ZP
	ldx #VMNEXT_SIZE-1
-  lda _vmnext,x
	sta VM_NEXT-1,x
	dex
	bpl -
	; make default opcode extension vector point to vmnext (noop)
	lda #VM_NEXT
	sta VM_EXTVEC
;	lda #0
;	sta VM_EXTVEC+1
endm

macro vmnext
	jmp VM_NEXT
endm


macro vmnext_pla
	jmp VM_NEXT-1
endm

;7
macro ifetch
	lda   (VM_IPBASE),y
	iny
endm




; ------------- core opcodes -------------

; to compile opcodes, .db <opcode_routine

align 256   ; we're probably at $8000 anyway (it makes sense to make this the first thing in a PRG bank)
            ; but just have to make sure
opcode_page:
	OPCODE_PC = $

end:	     ; use to get out of interpreter  (return and data stack irrelevant!)
	jmp __end

call:
	jmp __call

jump:
	sta VM_TEMP
	jmp __jump

execute:
	jmp __execute

;	tax
	; address in bytecode stream must be pre-decremented.
;   ifetch  ;high
; 	pha
;	ifetch  ;low
;	pha
; 	txa
;	rts

execasm:  ; addrh/l --    address must be predecremented.
	pha
	rts

exit:
	jmp __exit

twolit:
  	pha
droptwolit:
  	ifetch
lit:
  	pha
droplit:
  	ifetch
  	vmnext

fetch:
   tax
  	lda  0,x
  	vmnext

store:
  	tax
  	pla
	sta  0,x
drop:
  	vmnext_pla

plusstore:
	tax
	pla
	clc
	adc  0,x
	sta  0,x
  	vmnext_pla

plus:
	sta VM_TEMP   ;3
	pla           ;4
	clc           ;2
	adc VM_TEMP   ;3 = 12
	vmnext

;_sbtrkt:
;	sta VM_TEMP
;	pla
;	sec
;	sbc VM_TEMP
;	vmnext

andd:
	sta VM_TEMP
	pla
	and VM_TEMP
	vmnext

or:
	jmp __or

dup:
	pha
	vmnext

;moved to within 'store'
;_drop:
;	pla
;	vmnext

twodup:
   pha
droptwodup:
	tsx
	lda $102,x
over:
   pha
dropover:
	tsx
	lda $102,x
	vmnext

swap:
	jmp __swap

nip:
	tax
	pla
	txa
	vmnext

fourdiv:
	CMP	#$80
	ROR	A
twodiv:
	CMP	#$80
	ROR	A
	vmnext

fourmul:
	asl   a
twomul:
	asl   a
	vmnext

astore:
	sta VM_A
	pla
	sta VM_A+1
  	vmnext_pla

afetch:
	jmp __afetch




; branch instructions are relative.  a literal in the instruction stream is added to Y if the branch is taken (to THEN)
; iff, mif, for, and next are intimately coupled.  it's important not to mess with the "PLA" instructions
; as they serve multiple purposes.
; because branch and nobranch pop the stack, they can be used to implement WHILE and UNTIL. (and REPEAT and AGAIN with an extra pha)


r:
	pha
dropr:
	ldx VM_RSP
	lda 0,x
	vmnext

oplus:
	jmp __oplus

equals:
	jmp __equals

less:
	jmp __less

greater:
	jmp __greater

uless:
	jmp __uless

ugreater:
	jmp __ugreater

ostore:
	jmp __ostore

xor:
	jmp __xor

ext:
	jmp (VM_EXTVEC)


iff:
	tax
	beq	branch
	bne   nobranch

nif:
	tax
	bne   branch
	beq   nobranch


for:
	jmp __for

next:
	pha
dropnext:
	ldx	VM_RSP
	lda	0,x
	beq   endfor
	dec	0,x
branch: ;24
	tya                    ;2
	clc                    ;2
	adc   (VM_IPBASE),y    ;5
	tay                    ;2
  	vmnext_pla             ;19

dplus:
	tsx
	clc
	adc $102,x
	sta $102,x
	lda $101,x
	adc $103,x
	sta $103,x
twodrop:
	pla
  	vmnext_pla

false:
zero:
	pha
dropzero:
	lda	#0
   vmnext

true:
	pha
droptrue:
	lda	#$FF
	vmnext

fetchplus:  ;3+6+2+3+2+3
	pha
dropfetchplus:
	ldx	#0
	lda	(VM_A,x)
aplus:
	inc	VM_A
	beq +         ; save a cycle by not taking the branch 99.53% of the time
	vmnext
+
	inc	VM_A+1
	vmnext

storeplus:
	ldx	#0
	sta	(VM_A,x)
	inc	VM_A
	bne +
	inc	VM_A+1
+
	vmnext_pla


callasm:  ;29
	tax
   ifetch  ;high
 	sta VM_TEMP+1
	ifetch  ;low
	sta VM_TEMP
 	txa
	jmp (VM_TEMP)


; now outside page.  safe to put this stuff here.

endfor:
	inc	VM_RSP
nobranch:
	iny
  	vmnext_pla


; * v * ====== THE INNER LOOP ====== ^ _ ~

_vmnext:
	pla  ; for size optimization of some routines.  most opcodes jump to the byte after this.

	ldx	$FFFF, y        ; 4
	iny                   ; 2
	stx   VMNEXT_JMP      ; 3
	jmp   opcode_page     ; 3
                         ; +3 for the jmp to this routine = 15
	VMNEXT_SIZE = $ - _vmnext


; * v * ====== THE ENTRY POINT ====== ^ _ ~

; entry point!   simply put db statements under a JSR to this.
bytecode:
	tsx
	inx
	inx
	stx   VM_SPTMP      ; this allows us to safely get out of bytecode at any point.
	ldx   #VM_RTOP   ; initialize RSP
	stx   VM_RSP
; this is not currently used but it could be used if only callasm existed.
enter:
	tax
	pla
	sta   VM_IPBASE
	pla
	sta   VM_IPBASE+1
	ldy   #1         ; compensate for return address being 1 byte behind
	txa
interpret:   ;VM_IPBASE = address of code, Y = offset
	vmnext


;subroutines
;call
; push IPBASE and Y onto RS
; get new IPBASE and set Y to 0
; jump to interpret
;ret
; POP IPBASE and Y from RS
; jump to interpret

__call:  ;61
	sta VM_TEMP  ; save TOS

	ldx VM_RSP
	dex
	dex
	dex
	stx VM_RSP

 	sty 0,x
	lda VM_IPBASE
	sta 1,x
	lda VM_IPBASE+1
 	sta 2,x

__jump:

   ifetch  ;high
	tax
	lda (VM_IPBASE),y ; ifetch  ;low
	sta VM_IPBASE
	stx VM_IPBASE+1

	ldy #1
 	lda VM_TEMP  ; restore TOS

	vmnext

__execute:

	sta VM_TEMP

	ldx VM_RSP
	dex
	dex
	dex
	stx VM_RSP

 	sty 0,x
	lda VM_IPBASE
	sta 1,x
	lda VM_IPBASE+1
 	sta 2,x

	lda VM_TEMP
	sta VM_IPBASE
	pla
	sta VM_IPBASE+1
	
	ldy #0

	vmnext

; pop return address;  adds up IP and adds 2 to get effective address
;  useful for inline data or callbacks
popra: ; -- addrh/l
	pha
	ldx VM_RSP
	lda 0,x ; saved Y
	clc
	adc #2 ; because call does not pre-increment the saved Y
	sta VM_TEMP
	lda 1,x ; ip low
	adc VM_TEMP
	sta VM_TEMP
	lda 2,x ; ip high
	adc #0  ; add any carry
	pha
	lda VM_TEMP
	inx
	inx
	inx
	stx VM_RSP
	vmnext

;  asm equivalent - jsr to a label and have this be the first line of code.
;   the caller can put data or more code (bytecode or asm) underneath the jsr.
;   warning: doesn't work across page boundary (so don't put this at $__FC)
macro asmpopra
	pla  ; low
	clc
	adc #1
	; high is already on stack
endm




__end:
	ldx   VM_SPTMP
	txs
	rts

__exit:
	ldx VM_RSP
   ldy 1,x
	sty VM_IPBASE
	ldy 2,x
	sty VM_IPBASE+1
	ldy 0,x
	inx
	inx
	inx
	stx VM_RSP
	iny
	iny
	vmnext

__for:
	dec	VM_RSP
	ldx	VM_RSP
	sta	0,x
	dec   0,x
	vmnext_pla

__equals:
	sta	VM_TEMP
	pla
	cmp 	VM_TEMP
	bne	+
	lda	#$ff
	vmnext
+	lda	#$00
	vmnext


__greater:
	sta	VM_TEMP
	pla
	cmp 	VM_TEMP   ; compare tos to 2os
	beq	++
	bpl 	+
++	lda	#$00
	vmnext
+	lda	#$ff
	vmnext

__less:
	sta	VM_TEMP
	pla
	cmp 	VM_TEMP
	bmi	+
	lda	#$00
	vmnext
+	lda	#$ff
	vmnext


__uless:
	sta	VM_TEMP
	pla
	cmp 	VM_TEMP
	bcs	+
	lda	#$00
	vmnext
+	lda	#$ff
   vmnext

__ugreater:
	sta	VM_TEMP
	pla
	cmp 	VM_TEMP
	beq	++
	bcs	+
++	lda	#$00
	vmnext
+	lda	#$ff
	vmnext


__swap:
	tax
	pla
	sta VM_TEMP
	txa
	pha
	lda VM_TEMP
	vmnext

;__inca
;	inc	VM_A
;	bne +
;	inc	VM_A+1
;+
;	vmnext


; example of arbitrary asm routine for bytecode
; quickly move up to 256 bytes of data.  A is A+count after.
move:  ; A=src ... desth/l count8 --   A=A+count   B=dest
	sty VM_TEMP
	ldy #0
	tax
	pla
	sta VM_B
	pla
	sta VM_B+1
-      LDA (VM_A),Y ; move the remaining bytes
       STA (VM_B),Y
       INY
       DEX
       BNE -
   ; add transfer count to A.
	tya
	clc
	adc VM_A
	bne +
	inc VM_A+1
+
	sta VM_A

	;restore pc and tos
	ldy VM_TEMP
   vmnext_pla


__afetch:
	pha
	lda VM_A+1
	pha
	lda VM_A
	vmnext

__ostore:
	sta VM_O
	vmnext_pla

; not currently used to due to ridiculous slowness.  better to make custom routines for each property and/or
;  use some properties only within asm routines.
__fetcho:
	pha
	sty	VM_TEMP
	ifetch
	tay
	lda	(VM_O),y
	ldy	VM_TEMP
	vmnext

__storeo:
	tax
	sty	VM_TEMP
	ifetch
	tay
	txa
	sta	(VM_O),y
	ldy	VM_TEMP
	vmnext_pla

__xor:
	sta VM_TEMP
	pla
	eor VM_TEMP
	vmnext

__or:
	sta VM_TEMP
	pla
	ora VM_TEMP
	vmnext


__oplus:
	inc VM_O
	vmnext

include eighth\macros.s
